//------------------------------------------------------------------------------
// Copyright (C) 2007-2008 Samuel Lacroix
// This file is part of Visual Local History 2005
// 
// Visual Local History 2005 is free software; you can redistribute it
// and/or modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the License,
// or (at your option) any later version.
// 
// Visual Local History 2005 is distributed in the hope that it will be
// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with Visual Local History 2005; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
//------------------------------------------------------------------------------

#include "stdafx.h"
#include "Connect.h"
#include "HistoryWindow.h"
#include "FormConfig.h"
#include "Config.h"
#include "HistoryPrompt.h"

using namespace System::Text::RegularExpressions;
using namespace System::Net;


//==============================================================================
// Wizard-generated methods
//==============================================================================


void HistoryAddIn::Connect::OnAddInsUpdate(System::Array ^%custom)
{
}

void HistoryAddIn::Connect::OnBeginShutdown(System::Array ^%custom)
{
    config->Visible = toolWindow->Visible;
    config->SaveToAppConfig();
}

void HistoryAddIn::Connect::OnConnection(System::Object ^Application, ext_ConnectMode ConnectMode, System::Object ^AddInInst, System::Array ^%custom)
{
    _applicationObject = dynamic_cast<DTE2^>(Application);
    _addInInstance = dynamic_cast<AddIn^>(AddInInst);
    if(ConnectMode == ext_ConnectMode::ext_cm_UISetup)
    {
        array<Object^>^ contextGUIDs = gcnew array<Object^>(0);
        Commands2 ^commands = dynamic_cast<Commands2^>(_applicationObject->Commands);
        String ^toolsMenuName;

        try
        {
            //If you would like to move the command to a different menu, change the word "Tools" to the 
            //  English version of the menu. This code will take the culture, append on the name of the menu
            //  then add the command to that menu. You can find a list of all the top-level menus in the file
            //  CommandBar.resx.
            ResourceManager ^resourceManager = gcnew ResourceManager("HistoryAddIn.CommandBar", Assembly::GetExecutingAssembly());
            CultureInfo ^cultureInfo = gcnew System::Globalization::CultureInfo(_applicationObject->LocaleID);
            toolsMenuName = resourceManager->GetString(String::Concat(cultureInfo->TwoLetterISOLanguageName, "Tools"));
        }
        catch(...)
        {
            //We tried to find a localized version of the word Tools, but one was not found.
            //  Default to the en-US word, which may work for the current culture.
            toolsMenuName = "Tools";
        }

        //Place the command on the tools menu.
        //Find the MenuBar command bar, which is the top-level command bar holding all the main menu items:
        _CommandBars ^commandBars = dynamic_cast<CommandBars^>(_applicationObject->CommandBars);
        CommandBar ^menuBarCommandBar = dynamic_cast<CommandBar^>(commandBars["MenuBar"]);

        //Find the Tools command bar on the MenuBar command bar:
        CommandBarControl ^toolsControl = menuBarCommandBar->Controls[toolsMenuName];
        CommandBarPopup ^toolsPopup = dynamic_cast<CommandBarPopup^>(toolsControl);

        //This try/catch block can be duplicated if you wish to add multiple commands to be handled by your Add-in,
        //  just make sure you also update the QueryStatus/Exec method to include the new command names.
        try
        {	
            //Add a command to the Commands collection:
            Command ^command = commands->AddNamedCommand2(_addInInstance, "ShowLocalHistory", "Show Local History", "Executes the command for HistoryAddIn", true, 1106, contextGUIDs, (int)vsCommandStatus::vsCommandStatusSupported+(int)vsCommandStatus::vsCommandStatusEnabled, (int)vsCommandStyle::vsCommandStylePictAndText, vsCommandControlType::vsCommandControlTypeButton);

            //Add a control for the command to the tools menu:
            if((command) && (toolsPopup))
            {
                command->AddControl(toolsPopup->CommandBar, 1);
            }
        }
        catch(System::ArgumentException^)
        {
            //If we are here, then the exception is probably because a command with that name
            //  already exists. If so there is no need to recreate the command and we can 
            //  safely ignore the exception.
        }
    }
}

void HistoryAddIn::Connect::OnStartupComplete(System::Array ^%custom)
{
    Init();
}

void HistoryAddIn::Connect::OnDisconnection(ext_DisconnectMode removeMode, System::Array ^%custom)
{
    // We must delete the temporary files we have created, if they are still there
    for each (String^ FileName in TempFiles)
    {
        try
        {
            File::SetAttributes(FileName, File::GetAttributes(FileName) & ~FileAttributes::ReadOnly);
            File::Delete(FileName);
        }
        catch (...)
        {
        }
    }
}

void HistoryAddIn::Connect::Exec(String ^CmdName, vsCommandExecOption ExecuteOption, Object ^%VariantIn, Object ^%VariantOut, bool %handled)
{
    handled = false;

    if (ExecuteOption == vsCommandExecOption::vsCommandExecOptionDoDefault)
    {
        if (!CmdName->CompareTo("HistoryAddIn.Connect.ShowLocalHistory"))
        {
            Init();
            toolWindow->Visible = true;
            UpdateHistoryWindow();
            handled = true;
            return;
        }
    }
}

void HistoryAddIn::Connect::QueryStatus(String ^CmdName, vsCommandStatusTextWanted NeededText, vsCommandStatus %StatusOption, Object ^%CommandText)
{
    if(NeededText == vsCommandStatusTextWanted::vsCommandStatusTextWantedNone)
    {
        if(!CmdName->CompareTo("HistoryAddIn.Connect.ShowLocalHistory"))
        {
            StatusOption = (vsCommandStatus)(vsCommandStatus::vsCommandStatusSupported+vsCommandStatus::vsCommandStatusEnabled);
            return;
        }
    }
}


//==============================================================================
// Personal public methods
//==============================================================================


void HistoryAddIn::Connect::UpdateHistoryWindow()
{
    // Don't update if it's not visible
    if (!toolWindow->Visible)
        return;

    historyWindow->toolStripStatusLabel->Text = "Misc file";

    try 
    {
        // Get the solution history directory
        if (!GetHistoryPath())
        {
            historyWindow->treeView->Nodes->Clear();
            return;
        }

        // Get the history layout
        FileInfo^ fi = gcnew FileInfo(GetHistoryPath() + layoutFileName);
        if (!fi->Exists)
        {
            historyWindow->treeView->Nodes->Clear();
            return;
        }

        // Get the active document
        Document^ activeDocument = (realCurrentDocument) ? realCurrentDocument : _applicationObject->ActiveDocument;
        if (!activeDocument)
        {
            historyWindow->treeView->Nodes->Clear();
            return;
        }

        // Exclude multi-names documents (such as .frm/.frx)  
        if (activeDocument->ProjectItem->FileCount != 1) 
        {
            historyWindow->treeView->Nodes->Clear();
            return;
        }

        // Get source file and exclude misc files
        String^ SourcePath;
        if (activeDocument->ProjectItem->ContainingProject->UniqueName == "<MiscFiles>")
        {    
            SourcePath = (String^) BackupTempFileSources[activeDocument->FullName];
            if (!SourcePath)
            {
                historyWindow->treeView->Nodes->Clear();
                return;
            }
        }
        else
        {
            SourcePath = activeDocument->FullName;    
        }

        // Get backup directory name from layout file
        historyWindow->treeView->Tag = SourcePath;
        String^ RelativeFileName = DirRelativeDirTo(_applicationObject->Solution->FullName, SourcePath);
        String^ fileHistoryPath = ReadValueFromLayoutFile(RelativeFileName);
        historyWindow->toolStripStatusLabel->Text = RelativeFileName;
        if (!fileHistoryPath)
        {
            historyWindow->treeView->Nodes->Clear();
            return;
        }

        // Get the full name of the backup directory
        DirectoryInfo^ di = gcnew DirectoryInfo(GetHistoryPath() + fileHistoryPath);
        if (!di->Exists)
        {
            historyWindow->treeView->Nodes->Clear();
            return;
        }

        // Enumerate all files in backup directory, and sort them by timestamp
        Collections::SortedList^ sortedList = gcnew Collections::SortedList;
        for each (FileInfo^ fileInfo in di->GetFiles())
        {
            sortedList->Add(fileInfo->LastWriteTime, fileInfo); 
        }

        // Populate the history window
        historyWindow->treeView->Nodes->Clear();
        IDictionaryEnumerator^ Enumerator = sortedList->GetEnumerator();
        bool Selected = false;
        while (Enumerator->MoveNext())
        {
            // Get date and time in string format
            FileInfo^ fileInfo = (FileInfo^) Enumerator->Value;
            String^ dateString = fileInfo->LastWriteTime.ToString("d");
            String^ timeString = fileInfo->LastWriteTime.ToString("T");

            // Add or retrieve date node
            array<TreeNode^>^ dateNodes = historyWindow->treeView->Nodes->Find(dateString, false);
            TreeNode^ dateNode;
            if (dateNodes->Length == 0)
            {
                dateNode = historyWindow->treeView->Nodes->Insert(0, dateString, "", 0);
                dateNode->SelectedImageIndex = 0;
                dateNode->NodeFont = gcnew Font(historyWindow->treeView->Font, FontStyle::Bold);
                dateNode->Text = dateString;
            }
            else
            {
                dateNode = dateNodes[0];
            }

            // Add time node
            TreeNode^ timeNode = dateNode->Nodes->Insert(0, timeString, timeString, 1);
            timeNode->SelectedImageIndex = 1;
            timeNode->Tag = fileInfo->FullName;
            
            if (!Selected)
            {
                String^ TempFile = (String^) BackupTempFiles[timeNode->Tag];
                if (TempFile && TempFile == activeDocument->FullName)
                {
                    historyWindow->treeView->SelectedNode = timeNode; 
                    Selected = true;
                }
            }
        }
        if (!Selected && historyWindow->treeView->Nodes->Count > 0 && historyWindow->treeView->Nodes[0]->Nodes->Count > 0)
        {
            historyWindow->treeView->SelectedNode = historyWindow->treeView->Nodes[0]->Nodes[0];
        }
    }
    catch (Exception^)
    {
    	historyWindow->treeView->Nodes->Clear();
    }
    
    historyWindow->UpdateEnabledButtons();
}

String^ HistoryAddIn::Connect::DirRelativeDirTo(String^ From, String^ To)
{
    wchar_t Path[MAX_PATH + 1] = {0};
    pin_ptr<const wchar_t> szFrom = PtrToStringChars(From);
    pin_ptr<const wchar_t> szTo = PtrToStringChars(To);

    PathRelativePathToW(Path, szFrom, 0, szTo, 0);

    return gcnew String(Path);
}  

String^ HistoryAddIn::Connect::GetHistoryPath()
{
    try
    {
        historyPath = System::IO::Path::GetDirectoryName(_applicationObject->Solution->FullName) + "\\" + historyHiddenDirPrefix + "." + System::IO::Path::GetFileName(_applicationObject->Solution->FullName) + "\\";
    }
    catch(...)
    {
        historyPath = nullptr;
    }
    return historyPath;
} 

String^ HistoryAddIn::Connect::ReadValueFromLayoutFile(String^ Key)
{
    wchar_t szValue[64] = {0};
    if (!GetHistoryPath()) return nullptr;
    String^ filePath = GetHistoryPath() + layoutFileName;
    pin_ptr<const wchar_t> szKey = PtrToStringChars(Key);
    pin_ptr<const wchar_t> szPath = PtrToStringChars(filePath);
    GetPrivateProfileStringW(L"Layout", szKey, NULL, szValue, sizeof(szValue) / sizeof(wchar_t), szPath);
    return (szValue[0] == L'\0') ? nullptr : gcnew String(szValue);
}

void HistoryAddIn::Connect::CleanHistoryDirectory()
{
    if (!GetHistoryPath()) return;
    String^ filePath = GetHistoryPath() + layoutFileName;

    if (File::Exists(filePath))
    {
        // table key=String(history directory name of solution item) / value=String(relative path of solution item)
        Hashtable^ layoutTable = gcnew Hashtable;
        
        // List all files in the solution, while building the layout table
        for each (Project^ project in _applicationObject->Solution->Projects)
        {
            BuildLayout(layoutTable, project->ProjectItems);
        }
        
        // now, we have a clean table without any duplicate : we can create a new layout file which overwrites the previous one
        // TODO: create temporary->copy instead of delete->create
        if (File::Exists(filePath))
        {
            File::SetAttributes(filePath, File::GetAttributes(filePath) & ~FileAttributes::ReadOnly);
            File::Delete(filePath);
        }
        IDictionaryEnumerator^ Enumerator = layoutTable->GetEnumerator();
        while (Enumerator->MoveNext())
        {
            WriteValueToLayoutFile((String^) Enumerator->Value, (String^) Enumerator->Key);
        }     
        
        // clean all directories
        DirectoryInfo^ di = gcnew DirectoryInfo(GetHistoryPath());
        for each (DirectoryInfo^ dirInfo in di->GetDirectories())
        {
            // delete all directories that are not listed in the layout
            if (!layoutTable[dirInfo->Name])
            {
                RemoveAttributeRecursivelyInDir(FileAttributes::ReadOnly, dirInfo);
                Directory::Delete(dirInfo->FullName, true);
            }
            
            // delete old backups 
            for each (FileInfo^ fileInfo in dirInfo->GetFiles())
            {
                // delete old files (RetentionTimeInDays configuration)
                if (config->RetentionTimeInDays != 0 && (DateTime::Now - fileInfo->LastWriteTime).Days >= config->RetentionTimeInDays)
                {
                    File::SetAttributes(fileInfo->FullName, File::GetAttributes(fileInfo->FullName) & ~FileAttributes::ReadOnly);
                    File::Delete(fileInfo->FullName);
                }
            }  

        }   
        
        File::SetAttributes(filePath, File::GetAttributes(filePath) | FileAttributes::Hidden);
    }
}

bool HistoryAddIn::Connect::StringMatchPatterns(String^ Str, array<String^>^ Pattern)
{
    try
    {
        for each (String^ s in Pattern)
        {
            if (s->Length > 0)
            {
                if (Regex::IsMatch(Str, s))
                {    
                    return true;
                }
            }
        }
    }
    catch (...)
    {
    }
    return false;
}


String^ HistoryAddIn::Connect::RemoveQuotes(String^ Str)
{
    if (!Str)
    {
        return nullptr;
    }
    int FirstIndex = (Str && Str->Length > 0 && Str[0] == '\"') ? 1 : 0;
    int n = (Str && Str->Length > 1 && Str[Str->Length - 1] == '\"') ? Str->Length - FirstIndex - 1 : Str->Length - FirstIndex;
    if (n < Str->Length)
    {
        Str = Str->Substring(FirstIndex, n);
        return RemoveQuotes(Str);
    }
    else
    {
        return Str;
    }
}


bool HistoryAddIn::Connect::CheckForNewVersion()
{
    String^ LastReleaseString = gcnew String("Beta 3.0.0");
    try
    {
        HttpWebRequest^ projectSite = dynamic_cast<HttpWebRequest^>(WebRequest::Create("http://www.codeplex.com/VLH2005"));
        projectSite->Timeout = 1000; // 1 sec max;
        HttpWebResponse^ response = dynamic_cast<HttpWebResponse^>(projectSite->GetResponse());
        Stream^ stream = response->GetResponseStream();
        StreamReader^ streamReader = gcnew StreamReader(stream);
        String^ htmlCode = streamReader->ReadToEnd();
        response->Close();
        streamReader->Close();
        int i = htmlCode->IndexOf("(current version)");
        if (i > 0)
        {
            int j = htmlCode->Substring(0, i)->LastIndexOf('>');
            if (j > 0 && j < i)
            {
                String^ LastVersion = htmlCode->Substring(j+1, i-j-1)->Trim();
                if (LastVersion->ToLower() != LastReleaseString->ToLower())
                {
                    return true;
                }
            }
        }
    }
    catch(...)
    {
    }
    return false;
}

/*
void HistoryAddIn::Connect::GetAppNameAndArgs(String^ FullCommand, String^% AppName, String^% Args)
{
    try
    {
        int StartIndex = 0;
        int EndIndex = -1;
        String^ s = FullCommand->Trim();
        if (s->Length > 1)
        {
            if (s[0] == '\"')
            {
                StartIndex = 1;
                EndIndex = s->IndexOf('\"', 1);
            }
            else
            {
                EndIndex = s->IndexOf(' ', 1);
            }
        }
        AppName = ((EndIndex == -1) ? s->Substring(StartIndex) : s->Substring(StartIndex, EndIndex - StartIndex))->Trim();
        Args = (EndIndex == -1) ? gcnew String("") : s->Substring(EndIndex)->Trim();
    }
    catch (...)
    {
        AppName = gcnew String("");
        Args = gcnew String("");
    }
}
*/

//==============================================================================
// Personal private methods
//==============================================================================


void HistoryAddIn::Connect::Init()
{ 
    static bool InitDone = false;
    if (!InitDone) 
    {
        try
        {
            config = gcnew Config(_applicationObject);
            config->LoadFromAppConfig();
            config->SaveToAppConfig();
            
            formConfig = gcnew FormConfig;
            formConfig->ConnectObject = this;
            
            // if no diff tool in config, use the first detected tool
            if (config->DiffToolCommand->Length == 0 && config->DiffToolArgs->Length == 0)
            {
                if (formConfig->DetectedTools->Count > 0)
                {
                    IDictionaryEnumerator^ Enumerator = formConfig->DetectedTools->GetEnumerator();
                    Enumerator->MoveNext();
                    config->DiffToolCommand = (String^) Enumerator->Value;
                    config->DiffToolArgs = (String^) formConfig->PredefinedArgs[(String^) Enumerator->Key];
                    config->SaveToAppConfig();
                }
            }

            historyHiddenDirPrefix = ".VLH2005";
            layoutFileName = "layout";
            CreateHistoryWindow();
            BindEventHandlers();
            OnSolutionOpened(); // hack needed because OnSolutionOpened is not called if Visual is loading a solution at startup
            if (config->NewVersionCheck && CheckForNewVersion())
            {
                if (DialogResult::Yes == MessageBox::Show("A new version of Visual Local History 2005 is available. Do you want to download it ?", "Visual Local History 2005", System::Windows::Forms::MessageBoxButtons::YesNo, System::Windows::Forms::MessageBoxIcon::Question))
                {
                    System::Diagnostics::Process::Start("http://www.codeplex.com/VLH2005");
                }
            }
            InitDone = true;
        }
        catch (Exception^ e)
        {
        	MessageBox::Show("Initialization failed.\n" + e->Message, "Visual Local History 2005", MessageBoxButtons::OK, MessageBoxIcon::Error);
        }
    }
}

// Creates the tool window
void HistoryAddIn::Connect::CreateHistoryWindow()
{
    if (!toolWindow)
    {
        Object^ programmableObject = nullptr;
        Windows2^ windows2 = (Windows2^)_applicationObject->Windows;
        Assembly^ assembly = Assembly::GetExecutingAssembly();
        toolWindow = windows2->CreateToolWindow2(_addInInstance, assembly->Location, "HistoryAddIn.HistoryWindow", "Local History", "{A104A6EC-613C-476a-B989-415697D267A3}", programmableObject);
        historyWindow = (HistoryWindow^)toolWindow->Object;
        historyWindow->ApplicationObject = _applicationObject;  
        historyWindow->ConnectObject = this;
        historyWindow->Visible = true;
        toolWindow->Visible = config->Visible;
        UpdateHistoryWindow();
    }
}

// Set up event handlers
void HistoryAddIn::Connect::BindEventHandlers()
{
    if (!docEvents)
    {
        docEvents = _applicationObject->Events->DocumentEvents[nullptr];
        docEvents->DocumentSaved += gcnew _dispDocumentEvents_DocumentSavedEventHandler(this, &HistoryAddIn::Connect::OnDocumentSaved);
        docEvents->DocumentClosing += gcnew _dispDocumentEvents_DocumentClosingEventHandler(this, &HistoryAddIn::Connect::OnDocumentClosing);
    }

    if (!solEvents)
    {
        solEvents = _applicationObject->Events->SolutionEvents;
        solEvents->Opened += gcnew _dispSolutionEvents_OpenedEventHandler(this, &HistoryAddIn::Connect::OnSolutionOpened); 
        solEvents->AfterClosing += gcnew _dispSolutionEvents_AfterClosingEventHandler(this, &HistoryAddIn::Connect::OnSolutionClosed);  
        solEvents->BeforeClosing += gcnew _dispSolutionEvents_BeforeClosingEventHandler(this, &HistoryAddIn::Connect::OnSolutionClosing);
        solEvents->Renamed += gcnew _dispSolutionEvents_RenamedEventHandler(this, &HistoryAddIn::Connect::OnSolutionRenamed);
    }

    if (!winEvents)
    {
        winEvents = _applicationObject->Events->WindowEvents[nullptr];
        winEvents->WindowActivated += gcnew _dispWindowEvents_WindowActivatedEventHandler(this, &HistoryAddIn::Connect::OnWindowActivated); 
    }

    if (!projItemsEvents)
    {
        projItemsEvents = ((Events2^)(_applicationObject->Events))->ProjectItemsEvents;  
        projItemsEvents->ItemRenamed += gcnew _dispProjectItemsEvents_ItemRenamedEventHandler(this, &HistoryAddIn::Connect::OnItemRenamed); 
    }
}

// Write Key=Value in the layout file (ini format)
void HistoryAddIn::Connect::WriteValueToLayoutFile(String^ Key, String^ Value)
{
    String^ filePath = GetHistoryPath() + layoutFileName;
    Directory::CreateDirectory(GetHistoryPath());
    if (!File::Exists(filePath))
        File::Create(filePath)->Close();
    pin_ptr<const wchar_t> szKey = PtrToStringChars(Key);
    pin_ptr<const wchar_t> szValue = PtrToStringChars(Value);
    pin_ptr<const wchar_t> szPath = PtrToStringChars(filePath);
    WritePrivateProfileStringW(L"Layout", szKey, szValue, szPath);
}

// Build the layout table from a given ProjectItems object
void HistoryAddIn::Connect::BuildLayout(Hashtable^ layout, ProjectItems^ projectItems)
{
    for each (ProjectItem^ projectItem in projectItems)
    {
        // children
        if (projectItem->ProjectItems && projectItem->ProjectItems->Count > 0)
        {
            BuildLayout(layout, projectItem->ProjectItems);
        }

        // self
        if (projectItem->ContainingProject->UniqueName != "<MiscFiles>" && !StringMatchPatterns(System::IO::Path::GetFileName(projectItem->FileNames[0]), config->IgnoredExtsRegex) && projectItem->FileCount == 1)
        {
            String^ RelativeFileName;
            try
            {
                RelativeFileName = DirRelativeDirTo(_applicationObject->Solution->FullName, projectItem->FileNames[0]);
            }
            catch (ArgumentException^)
            {
                // no name
                return;
            }
            String^ DirName = ReadValueFromLayoutFile(RelativeFileName);
            if (DirName)
            {
                if (Directory::Exists(GetHistoryPath() + DirName))
                {
                    layout->Add(DirName, RelativeFileName);    
                }
            }
        }
    }
}

// Remove the given attributes in all files and subdirectories of the given dir
void HistoryAddIn::Connect::RemoveAttributeRecursivelyInDir(FileAttributes Attribute, DirectoryInfo^ di)
{
    for each (DirectoryInfo^ d in di->GetDirectories())
    {
        RemoveAttributeRecursivelyInDir(Attribute, d);  
    }
    for each (FileInfo^ d in di->GetFiles())
    {
        d->Attributes &= ~Attribute;
    }
}

// Called when a document is saved by the IDE
void HistoryAddIn::Connect::OnDocumentSaved(Document^ document)
{
    if (!GetHistoryPath()) return;
    
    // history available only for solution items
    if (document->ProjectItem->ContainingProject->UniqueName == "<MiscFiles>")
        return;

    // we dont support multi-names documents (such as .frm/.frx)
    if (document->ProjectItem->FileCount != 1) 
        return;

    // Drop unwanted documents (IgnoredExts configuration)
    if (StringMatchPatterns(System::IO::Path::GetFileName(document->FullName), config->IgnoredExtsRegex))
        return;
    
    // Associate relative directory with Guid directory
    String^ RelativeFileName = DirRelativeDirTo(_applicationObject->Solution->FullName, document->FullName);
    if (!ReadValueFromLayoutFile(RelativeFileName))
    {
        WriteValueToLayoutFile(RelativeFileName, Guid::NewGuid().ToString());
    }

    // Create backup diectory if it doesn't exist
    DirectoryInfo^ di = gcnew DirectoryInfo(GetHistoryPath() + ReadValueFromLayoutFile(RelativeFileName));
    if (!di->Exists)
    {
        di->Create();
        File::SetAttributes(di->FullName, (FileAttributes) (File::GetAttributes(di->FullName) | FileAttributes::Hidden));
        File::SetAttributes(GetHistoryPath(), (FileAttributes) (File::GetAttributes(GetHistoryPath()) | FileAttributes::Hidden));
    }

    // we don't create anything if the most recent backup is identical or too recent
    Collections::SortedList^ sortedList = gcnew Collections::SortedList;
    FileInfo^ MostRecent;
    for each (FileInfo^ fileInfo in di->GetFiles())
    {
        // delete old files (RetentionTimeInDays configuration)
        if (config->RetentionTimeInDays != 0 && (DateTime::Now - fileInfo->LastWriteTime).Days >= config->RetentionTimeInDays)
        {
            File::SetAttributes(fileInfo->FullName, File::GetAttributes(fileInfo->FullName) & ~FileAttributes::ReadOnly);
            File::Delete(fileInfo->FullName);
        }
        else
        {
            if (MostRecent)
            {
                if (fileInfo->LastWriteTime.CompareTo(MostRecent->LastWriteTime) > 0)
                {
                    MostRecent = fileInfo;   
                }
            }
            else
            {
                MostRecent = fileInfo;
            }
        }
    }  
    if (MostRecent)
    {  
        // Drop if last backup is too recent (MinTimeBetweenBackups configuration)
        if ((DateTime::Now - MostRecent->LastWriteTime).Seconds < config->MinTimeBetweenBackups) 
        {
            return;    
        }
        
        // Drop if identical
        String^ DataDoc = File::ReadAllText(document->FullName);
        String^ DataBackup = File::ReadAllText(MostRecent->FullName);
        if (DataDoc == DataBackup)
        {
            return;
        }
    }
    
    // Create a new backup
    String^ destination;
    do 
    {
        destination = di->FullName + "\\" + System::IO::Path::GetFileNameWithoutExtension(System::IO::Path::GetRandomFileName());
    } while (File::Exists(destination));
    File::Copy(document->FullName, destination);
    File::SetAttributes(destination, (FileAttributes) (File::GetAttributes(destination) | FileAttributes::Hidden | FileAttributes::ReadOnly));

    // Update the window
    UpdateHistoryWindow();
}

// Called when a document is about to be closed by the IDE
void HistoryAddIn::Connect::OnDocumentClosing(Document^ document)
{
    if (_applicationObject->Documents->Count <= 1)
    {
        realCurrentDocument = nullptr;
        UpdateHistoryWindow();
    }
}

// Called when a solution has just been opened in the IDE
void HistoryAddIn::Connect::OnSolutionOpened()
{    
    historyPath = nullptr;  
    realCurrentDocument = nullptr;
    if (!GetHistoryPath()) return;

    String^ filePath = GetHistoryPath() + layoutFileName;

    if (!File::Exists(filePath))
    {
        // it might be an upgrade from beta 2 or earlier (".history" folder)
        String^ historyOldDir = System::IO::Path::GetDirectoryName(_applicationObject->Solution->FullName) + "\\" + ".history" + "\\";
        if (Directory::Exists(historyOldDir))
        {
            // maybe it's the history of another solution, but we don't take care of that: we assume it's the history of the current solution
            // Move the .history folder
            // TODO: delete AFTER copy
            if (Directory::Exists(GetHistoryPath()))
            {
                DirectoryInfo^ di = gcnew DirectoryInfo(GetHistoryPath());
                RemoveAttributeRecursivelyInDir(FileAttributes::ReadOnly, di);
                Directory::Delete(GetHistoryPath(), true);
            }
            Directory::Move(historyOldDir, GetHistoryPath());
        }
    }
    
    if (!File::Exists(filePath))
    {
        // the solution might have been renamed outside the IDE. We must list all .VLH2005* folders, and see if their corresponding solution files exist. If we find at least one "orphan" folder, we prompt the user
        
        Generic::List<DirectoryInfo^>^ Found = gcnew Generic::List<DirectoryInfo^>;
        DirectoryInfo^ di = gcnew DirectoryInfo(System::IO::Path::GetDirectoryName(_applicationObject->Solution->FullName));
        for each (DirectoryInfo^ dirInfo in di->GetDirectories(historyHiddenDirPrefix + ".*"))
        {
            if (!File::Exists(dirInfo->Name->Substring(historyHiddenDirPrefix->Length + 1)))
            {
                Found->Add(dirInfo);
            }
        }
        
        if (Found->Count > 0)
        {
            HistoryPrompt^ historyPrompt = gcnew HistoryPrompt;
            for each (DirectoryInfo^ di in Found)
            {
                ListViewItem^ lvi = historyPrompt->listView1->Items->Add(di->Name->Substring(historyHiddenDirPrefix->Length + 1));
                lvi->SubItems->Add(di->FullName);
            }
            if (DialogResult::OK == historyPrompt->ShowDialog() && historyPrompt->listView1->SelectedItems->Count > 0)
            {
                Found[historyPrompt->listView1->SelectedIndices[0]]->MoveTo(GetHistoryPath());
            }
        }
    }

    CleanHistoryDirectory();
    UpdateHistoryWindow();
    historyWindow->UpdateEnabledButtons();
}

// Called when a solution has just been closed in the IDE
void HistoryAddIn::Connect::OnSolutionClosed()
{
    historyPath = nullptr;  
    realCurrentDocument = nullptr;
    UpdateHistoryWindow(); 
}

// Called when a solution is about to be closed in the IDE
void HistoryAddIn::Connect::OnSolutionClosing()
{
    CleanHistoryDirectory();
}
// Called by OnSolutionRenamed to list all files in the solution
void HistoryAddIn::Connect::BuildProjectItemsList(Generic::List<ProjectItem^>^ projectItemList, ProjectItems^ projectItems)
{
    for each (ProjectItem^ projectItem in projectItems)
    {
        if (projectItem->ProjectItems && projectItem->ProjectItems->Count > 0)
        {
            BuildProjectItemsList(projectItemList, projectItem->ProjectItems);
        }
        else
        {
            if (projectItem->ContainingProject->UniqueName != "<MiscFiles>" && projectItem->FileCount == 1)
            {
                projectItemList->Add(projectItem);   
            }
        }
    }
}

// Called when a solution is saved to a new name
void HistoryAddIn::Connect::OnSolutionRenamed(String^ oldName)
{
    String^ oldDir = System::IO::Path::GetDirectoryName(oldName) + "\\" + historyHiddenDirPrefix + "." + System::IO::Path::GetFileName(oldName) + "\\";
    String^ newDir = GetHistoryPath();
    try
    {
        if (newDir != oldDir && Directory::Exists(oldDir))
        {
            // Move the .history folder
            // TODO: delete AFTER copy
            if (Directory::Exists(newDir))
            {
                DirectoryInfo^ di = gcnew DirectoryInfo(newDir);
                RemoveAttributeRecursivelyInDir(FileAttributes::ReadOnly, di);
                Directory::Delete(newDir, true);
            }
            Directory::Move(oldDir, newDir);
            
            // The relative file names (in layout file) have changed, so we need to handle this
            Generic::List<ProjectItem^>^ projectItemList = gcnew Generic::List<ProjectItem^>;
            
            // List all files in the solution
            for each (Project^ project in _applicationObject->Solution->Projects)
            {
                BuildProjectItemsList(projectItemList, project->ProjectItems);
            }
            
            // Add new relative file names to the layout file
            for each (ProjectItem^ projectItem in projectItemList)
            {
                String^ FullNewName = projectItem->FileNames[0];
                String^ RelativeNewName = DirRelativeDirTo(_applicationObject->Solution->FullName, FullNewName);
                String^ RelativeOldName = DirRelativeDirTo(oldName, FullNewName);
                String^ guidDir = ReadValueFromLayoutFile(RelativeOldName);
                if (guidDir)
                {
                    WriteValueToLayoutFile(RelativeNewName, guidDir);
                }
            }
            
            // cleanup (removes old relative file names from the layout file)
            CleanHistoryDirectory();
        }
    }
    catch (Exception^ e)
    {
    	MessageBox::Show("Failed to move history information from " + oldDir + " to " + newDir + ".\n" + e->ToString() + " exception. Message : " + e->Message, "Visual Local History 2005", MessageBoxButtons::OK, MessageBoxIcon::Error);
    }
}

// Called when a window is activated in the IDE
void HistoryAddIn::Connect::OnWindowActivated(Window^ gotFocus, Window^ lostFocus)
{
    // - realCurrentDocument will be used by UpdateHistoryWindow
    //   (_applicationObject->ActiveDocument has not changed yet, so we can't use it)
    if (gotFocus->Document && realCurrentDocument != gotFocus->Document)
    {
        realCurrentDocument = gotFocus->Document;
    }
    UpdateHistoryWindow();
    historyWindow->UpdateEnabledButtons();
}

// Called when a project item is renamed by the IDE
void HistoryAddIn::Connect::OnItemRenamed(ProjectItem^ item, String^ oldName)
{
    // NB: if an item is renamed then the solution closed without saving, VS fails to open the file the next time the solution is opened, so we don't care

    // history available only for solution items
    if (item->ContainingProject->UniqueName == "<MiscFiles>")
        return;

    // we dont support multi-names documents (such as .frm/.frx)
    if (item->FileCount != 1) 
        return;

    // Get Guid directory of the old name
    oldName = System::IO::Path::GetDirectoryName(item->FileNames[0]) + "\\" + oldName;
    String^ RelativeFileName = DirRelativeDirTo(_applicationObject->Solution->FullName, oldName);
    String^ guidDir = ReadValueFromLayoutFile(RelativeFileName);
    if (!guidDir)
        return;

    // Add new name with same guid directory
    String^ RelativeNewFileName = DirRelativeDirTo(_applicationObject->Solution->FullName, item->FileNames[0]);
    WriteValueToLayoutFile(RelativeNewFileName, guidDir);

    CleanHistoryDirectory();
}
